/*
 * Copyright (C) 2008 GeoScheduler Team, as stated on <http://www.geoscheduler.org/authors>.
 * 
 * This file is part of GeoScheduler.
 * 
 * GeoScheduler is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GeoScheduler is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with GeoScheduler. If not, see <http://www.gnu.org/licenses/>.
 */
package org.geoscheduler.rest;

import java.util.Date;

import javax.annotation.Resource;
import javax.ws.rs.ConsumeMime;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.ProduceMime;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geoscheduler.commons.msgs.BasicMessageSeverities;
import org.geoscheduler.commons.msgs.ParametrizedMessage;
import org.geoscheduler.commons.msgs.types.AccountMsgs;
import org.geoscheduler.commons.msgs.types.GeneralMsgs;
import org.geoscheduler.exceptions.GSAccountCreationException;
import org.geoscheduler.exceptions.GSAuthenticationException;
import org.geoscheduler.exceptions.GSException;
import org.geoscheduler.model.Account;
import org.geoscheduler.rest.utils.MIMEConstants;
import org.json.JSONObject;

/**
 * REST API for accounts handling.
 * 
 * @author srakyi
 * @author lko
 */
@Resource
@Path("accounts")
public class AccountsResource extends AbstractResource {

	private final static Log LOG = LogFactory.getLog(AccountsResource.class);

	public AccountsResource() {
		super();
	}

	/**
	 * POST method constructs {@link Account} from given form, checks user's
	 * credentials and if it's ok, new account is persisted. Otherwise error
	 * message is returned.
	 * 
	 * @param jsonAccount	Account credentials marshalled in JSON.
	 * 
	 * @return	JSON object with error message, or created account.
	 */
	@POST
	@ConsumeMime(MIMEConstants.APP_JSON)
	@ProduceMime(MIMEConstants.APP_JSON)
	public JSONObject handlePost(JSONObject jsonAccount) {
		try {
			Account account = getMarshaller().unmarshall(new Account(), jsonAccount);
			return response(checkAndCreate(account));
		} catch (GSAccountCreationException ex) {
			LOG.error("failed to create account " + jsonAccount, ex);
			return response(new ParametrizedMessage(AccountMsgs.ACCOUNT_CREATION_FAILED, ex.getMessage()));
		} catch (GSAuthenticationException ex) {
			LOG.error("authentication failed for " + jsonAccount, ex);
			return response(new ParametrizedMessage(AccountMsgs.INVALID_ACCOUNT_CREDENTIALS, ex.getMessage()));
		} catch (Exception ex) {
			LOG.error("general exception occured for put " + jsonAccount, ex);
			return response(new ParametrizedMessage(GeneralMsgs.UNKNOWN_ERROR, ex.getMessage()));
		}
	}
	
	@GET
	@Path("{id}")
	@ProduceMime(MIMEConstants.APP_JSON)
	public JSONObject handleGet(@PathParam("id") Long id) {
		try {
			Account acc = getAccountDAO().loadById(id);
			if (acc != null) {
				return response(acc);
			} else {
				return response(new ParametrizedMessage(AccountMsgs.ACCOUNT_NOT_FOUND, BasicMessageSeverities.ERROR));
			}
		} catch (Exception ex) {
			LOG.error("failed to get account #" + id, ex);
			return response(new ParametrizedMessage(GeneralMsgs.UNKNOWN_ERROR, ex.getMessage()));
		}
	}

	/**
	 * Checks account's credentials and if it's ok and account isn't already in database, it is inserted.
	 * If account already was in database it is returned (no error raises in this case! - we need idempotency =
	 * = if you post same account credentials twice, it's created once without errors).
	 *  
	 * @param account	{@link Account} to be checked and inserted.
	 * 
	 * @return	{@link Account} that was found or inserted into database.
	 * 
	 * @throws GSException
	 */
	private Account checkAndCreate(Account account) throws GSException {
		if (!getAuthProvider().checkCredentials(account)) {
			throw new GSAuthenticationException("Invalid account credentials!");
		}
		
		Account oldAcc = getAccountDAO().loadByLogin(account.getLogin());
		if (oldAcc != null) {
			if (oldAcc.getAccountType() != account.getAccountType()) {
				throw new GSAccountCreationException("Account already exists.");
			} else {
				// in case that account already existed and had same login & type (~ idempotence)
				return oldAcc;
			}
		}
		
		account.setCreated(new Date());
		account.setAccessed(new Date());
		return getAccountDAO().insert(account);
	}
}
